﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Threading;

namespace QuailDashboard.View.Models
{
    /// <summary>
    /// Base class for Models.  Manages the dependency mechanism.
    /// </summary>
    /// <remarks>
    /// From Nikhil Kothari at http://www.nikhilk.net.
    /// </remarks>
    [DataContract]
    public abstract class Model : INotifyPropertyChanged
    {
        private readonly SynchronizationContext _syncContext;
        private static readonly Dictionary<string, PropertyChangedEventArgs> EventArgsMap = new Dictionary<string, PropertyChangedEventArgs>();

        /// <summary>
        /// Initializes a new instance of the <see cref="Model"/> class.
        /// </summary>
        protected Model()
        {
            _syncContext = SynchronizationContext.Current;
        }

        /// <summary>
        /// Fired when a property changes (implements INotifyPropertyChanged)
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises a change notification event to signal a change in the
        /// specified property's value.
        /// </summary>
        /// <param name="propertyName">The property that has changed.</param>
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (String.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }

            if (PropertyChanged == null)
            {
                return;
            }

            _syncContext.Post(
                state =>
                    {
                        if (PropertyChanged != null)
                            PropertyChanged(this, GetEventArgs(propertyName));
                    },
                null);
        }

        /// <summary>
        /// Raises a change notification event to signal a change in the
        /// specified properties.
        /// </summary>
        /// <param name="propertyNames">The properties that have changed.</param>
        protected void NotifyPropertyChanged(params string[] propertyNames)
        {
            if (PropertyChanged == null)
                return;

            if (propertyNames == null || propertyNames.Length == 0)
            {
                _syncContext.Post(
                    state =>
                        {
                            if (PropertyChanged != null)
                                PropertyChanged(this, new PropertyChangedEventArgs(string.Empty));
                        },
                    null);
            }
            else
            {
                _syncContext.Post(
                    delegate
                        {
                            foreach (var propertyName in propertyNames)
                            {
                                if (PropertyChanged != null)
                                    PropertyChanged(this, GetEventArgs(propertyName));
                            }
                        },
                        null);
            }
        }

        private static PropertyChangedEventArgs GetEventArgs(string propertyName)
        {
            PropertyChangedEventArgs pe;
            if (EventArgsMap.TryGetValue(propertyName, out pe) == false)
            {
                pe = new PropertyChangedEventArgs(propertyName);
                EventArgsMap[propertyName] = pe;
            }

            return pe;
        }
    }
}
